﻿using System;

namespace OmniKits.Extensions
{
    using NullGuard;

    [NullGuard(ValidationFlags.None)]
    public static class NumberExtensions
    {
#pragma warning disable 1591

        #region MAGIC, NO TOUCH!

        // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn

        private static readonly int[] MultiplyDeBruijnBitPosition = new[] 
        {
            0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
            8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
        };
        private static int GetHighestBitPositionCore(uint v)
        {
            v |= v >> 1;
            v |= v >> 2;
            v |= v >> 4;
            v |= v >> 8;
            v |= v >> 16;

            return MultiplyDeBruijnBitPosition[(v * 0x07C4ACDDU) >> 27];
        }

        public static int GetHighestBitPosition(this int source)
        {
            return GetHighestBitPositionCore((uint)source);
        }

        #endregion

        const decimal KiUnit = 1024;
        const decimal MiUnit = KiUnit * 1024;
        const decimal GiUnit = MiUnit * 1024;
        const decimal TiUnit = GiUnit * 1024;
        const decimal PiUnit = TiUnit * 1024;
        const decimal EiUnit = PiUnit * 1024;

        const string BasicBinaryPrefixFormat = "0.00";

        #region regular numeric

        #region Decimal

        public static string ToKi(this decimal value)
        {
            return (value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this decimal value)
        {
            return (value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this decimal value)
        {
            return (value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToTi(this decimal value)
        {
            return (value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToPi(this decimal value)
        {
            return (value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToEi(this decimal value)
        {
            return (value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region Double

        public static string ToKi(this double value)
        {
            return ((decimal)value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this double value)
        {
            return ((decimal)value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this double value)
        {
            return ((decimal)value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToTi(this double value)
        {
            return ((decimal)value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToPi(this double value)
        {
            return ((decimal)value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToEi(this double value)
        {
            return ((decimal)value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region Int32

        public static int FlipBits(this int value) => (int)FlipBits((uint)value);

        public static string ToKi(this int value)
        {
            return (value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this int value)
        {
            return (value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this int value)
        {
            return (value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region Int64

        public static string ToKi(this long value)
        {
            return (value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this long value)
        {
            return (value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this long value)
        {
            return (value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToTi(this long value)
        {
            return (value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToPi(this long value)
        {
            return (value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToEi(this long value)
        {
            return (value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #endregion

        #region nullable numeric

        #region Decimal

        public static string ToKi(this decimal? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this decimal? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this decimal? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToTi(this decimal? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToPi(this decimal? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToEi(this decimal? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region Double

        public static string ToKi(this double? value)
        {
            if (!value.HasValue) return null;
            return ((decimal)value.Value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this double? value)
        {
            if (!value.HasValue) return null;
            return ((decimal)value.Value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this double? value)
        {
            if (!value.HasValue) return null;
            return ((decimal)value.Value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToTi(this double? value)
        {
            if (!value.HasValue) return null;
            return ((decimal)value.Value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToPi(this double? value)
        {
            if (!value.HasValue) return null;
            return ((decimal)value.Value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToEi(this double? value)
        {
            if (!value.HasValue) return null;
            return ((decimal)value.Value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region Int32

        public static string ToKi(this int? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this int? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this int? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region Int64

        public static string ToKi(this long? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToMi(this long? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToGi(this long? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToTi(this long? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToPi(this long? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }
        public static string ToEi(this long? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #endregion

#if IncludeNonClsCompliantElements
        [CLSCompliant(false)]
        public static int GetHighestBitPosition(this uint source)
        {
            return GetHighestBitPositionCore(source);
        }

        #region regular numeric

        #region UInt32

        [CLSCompliant(false)]
        public static uint FlipBits(this uint value)
        {
            value = ((value & 0x55555555) << 1) | ((value >> 1) & 0x55555555);
            value = ((value & 0x33333333) << 2) | ((value >> 2) & 0x33333333);
            value = ((value & 0x0F0F0F0F) << 4) | ((value >> 4) & 0x0F0F0F0F);
            value = (value << 24) | ((value & 0xFF00) << 8) |
                ((value >> 8) & 0xFF00) | (value >> 24);

            return value;
        }

        [CLSCompliant(false)]
        public static string ToKi(this uint value)
        {
            return (value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToMi(this uint value)
        {
            return (value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToGi(this uint value)
        {
            return (value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region UInt64

        [CLSCompliant(false)]
        public static string ToKi(this ulong value)
        {
            return (value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToMi(this ulong value)
        {
            return (value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToGi(this ulong value)
        {
            return (value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToTi(this ulong value)
        {
            return (value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToPi(this ulong value)
        {
            return (value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToEi(this ulong value)
        {
            return (value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #endregion

        #region nullable numeric

        #region UInt32

        [CLSCompliant(false)]
        public static string ToKi(this uint? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToMi(this uint? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToGi(this uint? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #region UInt64

        [CLSCompliant(false)]
        public static string ToKi(this ulong? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / KiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToMi(this ulong? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / MiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToGi(this ulong? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / GiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToTi(this ulong? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / TiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToPi(this ulong? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / PiUnit).ToString(BasicBinaryPrefixFormat);
        }

        [CLSCompliant(false)]
        public static string ToEi(this ulong? value)
        {
            if (!value.HasValue) return null;
            return (value.Value / EiUnit).ToString(BasicBinaryPrefixFormat);
        }

        #endregion

        #endregion
#endif
    }
}
